﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GenericGA.extmath;
using GenericGA.reader.regex;
using GenericGA.reader.tree;

namespace GenericGA.reader
{
    /// <summary>
    /// The reader class of the phenotype building functions. This is used to read values of various types in order to use them for the construction of the phenotype or candidate solution. The values are read off chromosomes and different chromosomes yield different values.
    /// </summary>
    public class Reader
    {

        /// <summary>
        /// Bits which the reader reads.
        /// </summary>
        private readonly IEnumerator<bool> bits;

        /// <summary>
        /// Constructor which takes an infinite supply of bits.
        /// </summary>
        /// <param name="bits">An enumeration of bits which can be read indefinitely.</param>
        public Reader(IEnumerable<bool> bits)
        {
            this.bits = bits.GetEnumerator();
        }

        /// <summary>
        /// Read a boolean value.
        /// </summary>
        /// <returns>A boolean value.</returns>
        public bool ReadBoolean()
        {
            bits.MoveNext();
            return bits.Current;
        }

        /// <summary>
        /// Read an integer between 0 and max both inclusive.
        /// </summary>
        /// <param name="max">The maximum number which can be returned.</param>
        /// <returns>An integer between 0 and max.</returns>
        public long ReadInteger(long max)
        {
            if (max < 0)
            {
                throw new ArgumentException();
            }

            if (max == 0)
            {
                return 0;
            }

            int bitLength = ExtMath.MinBits(max);

            long num = 0;
            long exp = 1;
            for (long i = 0; i < bitLength; i++)
            {
                if (ReadBoolean())
                {
                    num += exp;
                }
                exp *= 2;
            }

            return num % (max + 1);
        }

        /// <summary>
        /// Read an index to an array of a given length.
        /// </summary>
        /// <param name="length">The length of the array to index.</param>
        /// <returns>The index to the array.</returns>
        public long ReadIndex(long length)
        {
            if (length <= 0)
            {
                throw new ArgumentException();
            }

            if (length == 1)
            {
                return 0;
            }

            return ReadInteger(length - 1);
        }

        /// <summary>
        /// Read an integer between min and max both inclusive.
        /// </summary>
        /// <param name="min">The smallest number which can be read.</param>
        /// <param name="max">The largest number which can be read.</param>
        /// <returns>An integer between min and max.</returns>
        public long ReadInteger(long min, long max)
        {
            if (min > max)
            {
                throw new ArgumentException();
            }

            return ReadInteger(max - min) + min;
        }

        /// <summary>
        /// Read a float between min and max with increments of inc such that min is always included but the largest number returned is the last whole increment which is less than or equal to max, starting from min. If inc is greater than the difference between min and max then only min will be returned.
        /// </summary>
        /// <param name="min">The smallest number which can be read.</param>
        /// <param name="max">The largest number which can be read.</param>
        /// <param name="epsilon">The increment between each number in the range, also known as the resolution.</param>
        /// <returns>A float between min and max with increments inc.</returns>
        public double ReadFloat(double min, double max, double epsilon)
        {
            if (min > max)
            {
                throw new ArgumentException();
            }
            if (epsilon <= 0)
            {
                throw new ArgumentException();
            }

            return ReadIndex((long)((max - min) / epsilon) + 1) * epsilon + min;
        }

        /// <summary>
        /// Read an element from the provided choice of elements.
        /// </summary>
        /// <typeparam name="E">The type of the elements.</typeparam>
        /// <param name="choice">An array of elements to choose from.</param>
        /// <returns>An element from choice.</returns>
        public E ReadElem<E>(params E[] choice)
        {
            if (choice.Length == 0)
            {
                throw new ArgumentException();
            }
            int i = (int)ReadIndex(choice.Length);
            return choice[i];
        }

        /// <summary>
        /// Read an element from the provided choice of elements.
        /// </summary>
        /// <typeparam name="E">The type of the elements.</typeparam>
        /// <param name="choice">An array of elements to choose from.</param>
        /// <returns>An element from choice.</returns>
        public E ReadElem<E>(List<E> choice)
        {
            if (choice.Count == 0)
            {
                throw new ArgumentException();
            }
            int i = (int)ReadIndex(choice.Count);
            return choice[i];
        }

        /// <summary>
        /// Read a permutation of a given length of an ordered permutation.
        /// </summary>
        /// <typeparam name="E">The type of the elements in the permutation.</typeparam>
        /// <param name="orderedPerm">The ordered permutation the new permutation is to be based on.</param>
        /// <param name="permLength">The length of the permutation.</param>
        /// <returns>The permutation.</returns>
        public List<E> ReadPermutation<E>(List<E> orderedPerm, int permLength)
        {
            if (permLength <= 0)
            {
                throw new ArgumentException();
            }

            List<E> elemLeft = new List<E>(orderedPerm);
            List<E> perm = new List<E>(permLength);
            for (int i = 0; i < permLength; i++)
            {
                int index = (int)ReadIndex(elemLeft.Count);
                perm.Add(elemLeft[index]);
                elemLeft.RemoveAt(index);
            }
            return perm;
        }

        /// <summary>
        /// Read a permutation of a given ordered permutation of the same length.
        /// </summary>
        /// <typeparam name="E">The type of the elements in the permutation.</typeparam>
        /// <param name="orderedPerm">The ordered permutation the new permutation is to be based on.</param>
        /// <returns>The permutation.</returns>
        public List<E> ReadPermutation<E>(List<E> orderedPerm)
        {
            return ReadPermutation(orderedPerm, orderedPerm.Count);
        }

        /// <summary>
        /// Read a permutation of a given length of an ordered permutation.
        /// </summary>
        /// <typeparam name="E">The type of the elements in the permutation.</typeparam>
        /// <param name="orderedPerm">The ordered permutation the new permutation is to be based on.</param>
        /// <param name="permLength">The length of the permutation.</param>
        /// <returns>The permutation.</returns>
        public List<E> ReadPermutation<E>(E[] orderedPerm, int permLength)
        {
            return ReadPermutation(new List<E>(orderedPerm), permLength);
        }

        /// <summary>
        /// Read a permutation of a given ordered permutation of the same length.
        /// </summary>
        /// <typeparam name="E">The type of the elements in the permutation.</typeparam>
        /// <param name="orderedPerm">The ordered permutation the new permutation is to be based on.</param>
        /// <returns>The permutation.</returns>
        public List<E> ReadPermutation<E>(params E[] orderedPerm)
        {
            return ReadPermutation(orderedPerm, orderedPerm.Length);
        }

        /// <summary>
        /// Read a list of elements.
        /// </summary>
        /// <typeparam name="E">The type of the elements in the list.</typeparam>
        /// <param name="elems">The elements the list should be made of.</param>
        /// <param name="length">The length of the list.</param>
        /// <returns>The list.</returns>
        public List<E> ReadList<E>(int length, List<E> elems)
        {
            List<E> l = new List<E>();
            for (int i = 0; i < length; i++)
            {
                E elem = ReadElem<E>(elems);
                l.Add(elem);
            }
            return l;
        }

        /// <summary>
        /// Read a list of elements.
        /// </summary>
        /// <typeparam name="E">The type of the elements in the list.</typeparam>
        /// <param name="elems">The elements the list should be made of.</param>
        /// <param name="length">The length of the list.</param>
        /// <returns>The list.</returns>
        public List<E> ReadList<E>(int length, params E[] elems)
        {
            List<E> l = new List<E>();
            for (int i = 0; i < length; i++)
            {
                E elem = ReadElem<E>(elems);
                l.Add(elem);
            }
            return l;
        }

        /// <summary>
        /// Read a list of elements which matches a regular expression.
        /// </summary>
        /// <typeparam name="E">The type of the elements in the list.</typeparam>
        /// <param name="regex">The regular expression the list should match.</param>
        /// <returns>The list.</returns>
        public List<E> ReadList<E>(Regex<E> regex)
        {
            return regex.Generate(this);
        }

        /// <summary>
        /// Read a tree of nodes which is described by the provided rules.
        /// </summary>
        /// <typeparam name="N">The class type of the nodes.</typeparam>
        /// <param name="rules">The template of the tree.</param>
        /// <param name="maxDepth">The maximum number of nodes in the tree for each template type.</param>
        /// <returns>The root of the tree.</returns>
        public N ReadTree<N>(TreeTemplate<N> template, Dictionary<string, int> maxNodes) where N : class
        {
            return template.Generate(this, maxNodes);
        }

    }
}
